﻿using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sharp.Interprocess.MemoryManager
{
    [ProtoContract(AsReferenceDefault = true)]
    public class GeneralTree<T> : IEnumerable<T>
        where T : IEquatable<T>
    {
        [ProtoMember(1,AsReference = true)]
        public T Data { get; set; }

        [ProtoMember(2,AsReference = true)]
        public ICollection<GeneralTree<T>> ChildNodes { get; private set; }

        [ProtoMember(3,AsReference = true)]
        public GeneralTree<T> Parent { get; private set; }

        [ProtoIgnore]
        public bool IsLeafNode
        {
            get
            {
                return !ChildNodes.Any();
            }
        }        

        [ProtoIgnore]
        public IEnumerable<GeneralTree<T>> Ancestors
        {
            get
            {
                GeneralTree<T> currentNode = this;
                var parentPath = new List<GeneralTree<T>>();
                
                while(currentNode.Parent != null)
                {
                    parentPath.Add(currentNode);
                    currentNode = currentNode.Parent;
                }

                return parentPath;
            }
        }

        #region Constructor(s)

        private GeneralTree()
        {
            ChildNodes = new List<GeneralTree<T>>();
        }

        public GeneralTree(T nodeData)
            : this(nodeData,new List<GeneralTree<T>>())
        {
        }

        public GeneralTree(T nodeData,IEnumerable<GeneralTree<T>> childNodes)
        {            
            Data = nodeData;
            ChildNodes = new List<GeneralTree<T>>(childNodes);
        }
        #endregion

        #region Public Members
        public GeneralTree<T> FindNode(Func<T, bool> predicate)
        {
            if (predicate(Data))
            {
                return this;
            }

            foreach (var childNode in ChildNodes)
            {
                var foundNode = childNode.FindNode(predicate);
                if (foundNode != null)
                {
                    return foundNode;
                }
            }

            return null;
        }

        public GeneralTree<T> Add(GeneralTree<T> childNode)
        {
            childNode.Parent = this;
            ChildNodes.Add(childNode);
            return childNode;
        }

        public GeneralTree<T> Add(T nodeData)
        {
            return Add(new GeneralTree<T>(nodeData));
        }

        public bool Remove(GeneralTree<T> node)
        {
            throw new NotImplementedException();
        }



        #endregion


        #region IEnumerable<T> Implementation
        public IEnumerator<T> GetEnumerator()
        {
            var allNodes = GetAllNodes();
            return allNodes.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            var allNodes = GetAllNodes();
            return allNodes.GetEnumerator();            
        }

        #endregion

        #region Helper Methods

        private IEnumerable<T> GetAllNodes()
        {
            var nodeQueue = new List<T>();

            nodeQueue.Add(Data);
            foreach (var childNode in ChildNodes)
            {
                nodeQueue.AddRange(childNode.GetAllNodes());
            }

            return nodeQueue;
        }

        #endregion
    }
}
